﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using DomainDriver.CommonUtilities.Reflectors;

namespace DomainDriver.CommonUtilities.Collections
{
    public static class CollectionUtilities
    {
        public const string DefaultSeparator = ", ";

        public static IEqualityComparer<object> GetUntypedEqualityComparer<T>(this IEqualityComparer<T> typedComparer)
        {
            return new UntypedEqualityComparer<T>(typedComparer);
        }

        public static bool AreUnorderedCollectionsEqual<T>(this IEnumerable<T> firstCollection, IEnumerable<T> secondCollection)
        {
            return AreUnorderedCollectionsEqual<T>(firstCollection, secondCollection, null);
        }

        public static bool AreUnorderedCollectionsEqual<T>(this IEnumerable<T> firstCollection, IEnumerable<T> secondCollection, IEqualityComparer<T> equalityComparer)
        {
            if (firstCollection == null)
            { throw new ApplicationException(); }
            if (secondCollection == null)
            { throw new ApplicationException(); }

            if (firstCollection.Count() != secondCollection.Count())
            { return false; }

            foreach (T firstElement in firstCollection)
            {
                // LATER: If elements are collections, add nested call to AreUnorderedUntypedCollectionsEqual

                if (equalityComparer != null)
                {
                    if (!secondCollection.Contains(firstElement, equalityComparer))
                    { return false; }
                }
                else
                {
                    if (!secondCollection.Contains(firstElement))
                    { return false; }
                }
            }
            return true;
        }

        public static bool AreUnorderedUntypedCollectionsEqual(this ICollection firstCollection, ICollection secondCollection)
        {
            return AreUnorderedUntypedCollectionsEqual(firstCollection, secondCollection, null);
        }

        public static bool AreUnorderedUntypedCollectionsEqual(this ICollection firstCollection, ICollection secondCollection, IEqualityComparer<object> equalityComparer)
        {
            if (firstCollection == null)
            { throw new ApplicationException(); }
            if (secondCollection == null)
            { throw new ApplicationException(); }

            if (firstCollection.Count != secondCollection.Count)
            { return false; }

            IEnumerable<object> secondObjectEnumerable = secondCollection.Cast<object>();

            foreach (object firstElement in firstCollection)
            {
                // LATER: If elements are collections, add nested call to AreUnorderedUntypedCollectionsEqual

                if (equalityComparer != null)
                {
                    if (!secondObjectEnumerable.Contains(firstElement, equalityComparer))
                    { return false; }
                }
                else
                {
                    if (!secondObjectEnumerable.Contains(firstElement))
                    { return false; }
                }
            }
            return true;
        }

        public static bool AreUnorderedDictionariesEqual<K, T>(this IDictionary<K, T> firstDictionary, IDictionary<K, T> secondDictionary)
        {
            return AreUnorderedDictionariesEqual<K, T>(firstDictionary, secondDictionary, null);
        }

        public static bool AreUnorderedDictionariesEqual<K, T>(this IDictionary<K, T> firstDictionary, IDictionary<K, T> secondDictionary, IEqualityComparer<object> valueEqualityComparer)
        {
            if (firstDictionary == null)
            { throw new ApplicationException(); }
            if (secondDictionary == null)
            { throw new ApplicationException(); }

            if (!AreUnorderedCollectionsEqual<K>(firstDictionary.Keys, secondDictionary.Keys))
            { return false; }

            foreach (K key in firstDictionary.Keys)
            {
                T firstElement = firstDictionary[key];
                T secondElement = secondDictionary[key];

                ICollection firstCollection = firstElement as ICollection;
                ICollection secondCollection = secondElement as ICollection;

                if (firstCollection != null)
                {
                    if (valueEqualityComparer != null)
                    {
                        if (!firstCollection.AreUnorderedUntypedCollectionsEqual(secondCollection, valueEqualityComparer))
                        { return false; }
                    }
                    else
                    {
                        if (!firstCollection.AreUnorderedUntypedCollectionsEqual(secondCollection))
                        { return false; }
                    }
                }
                else
                {
                    if (valueEqualityComparer != null)
                    {
                        if (!valueEqualityComparer.Equals(firstElement, secondElement))
                        { return false; }
                    }
                    else
                    {
                        if (!firstElement.Equals(secondElement))
                        { return false; }
                    }
                }
            }
            return true;
        }

        public static void CombineDictionariesInPlace<K, V>(this IDictionary<K, V> result, IDictionary<K, V> toCombine)
        {
            if ((result == null) || (toCombine == null))
            { return; }

            foreach (K key in toCombine.Keys)
            {
                if (result.ContainsKey(key))
                { continue; }
                result.Add(key, toCombine[key]);
            }
        }

        public static IDictionary<K, V> CombineDictionariesToNew<K, V>(this IDictionary<K, V> primary, IDictionary<K, V> secondary)
        {
            Dictionary<K, V> output = new Dictionary<K, V>();

            CombineDictionariesInPlace(output, primary);
            CombineDictionariesInPlace(output, secondary);
            return output;
        }

        public static IDictionary<KEY, KEYED_OBJECT> ConvertCollectionOfKeyedObjects<KEY, KEYED_OBJECT>(this IEnumerable<KEYED_OBJECT> keyedObjects)
            where KEYED_OBJECT : IKeyedObject<KEY>
        {
            if (keyedObjects == null)
            { throw new ApplicationException(); }

            Dictionary<KEY, KEYED_OBJECT> dictionary = new Dictionary<KEY, KEYED_OBJECT>();
            foreach (KEYED_OBJECT keyValueObject in keyedObjects)
            {
                dictionary.Add(keyValueObject.Key, keyValueObject);
            }
            return dictionary;
        }

        public static string GenerateUniqueCollectionId<T>(this IEnumerable<T> collection)
        {
            return GenerateUniqueCollectionId(collection, DefaultSeparator);
        }

        public static string GenerateUniqueCollectionId<T>(this IEnumerable<T> collection, string separator)
        {
            SortedDictionary<string, string> sortedStrings = new SortedDictionary<string, string>();
            foreach (T element in collection)
            {
                string elementAsString = element.ToString();
                sortedStrings.Add(elementAsString, elementAsString);
            }

            string id = string.Empty;
            bool first = true;
            foreach (string elementAsString in sortedStrings.Keys)
            {
                id += first ? elementAsString : (separator + elementAsString);
                first = false;
            }
            return id;
        }

        public static string GenerateUniqueCollectionName<T>(this IEnumerable<T> collection)
            where T : INamedObject
        {
            return GenerateUniqueCollectionName(collection, DefaultSeparator);
        }

        public static string GenerateUniqueCollectionName<T>(this IEnumerable<T> collection, string separator)
            where T : INamedObject
        {
            SortedDictionary<string, string> sortedStrings = new SortedDictionary<string, string>();
            foreach (T element in collection)
            {
                sortedStrings.Add(element.Name, element.Name);
            }

            string id = string.Empty;
            bool first = true;
            foreach (string name in sortedStrings.Keys)
            {
                id += first ? name : (separator + name);
                first = false;
            }
            return id;
        }

        public static int Convert(this bool[] bools)
        {
            int numberOfBools = 32;
            int bits = 0;

            for (int boolIndex = 0; boolIndex < numberOfBools; boolIndex++)
            {
                int bitMask = (1 << boolIndex);
                if (bools[boolIndex])
                {
                    bits = bits | bitMask;
                }
                bool value = (bitMask == (bitMask & bits));
                bools[boolIndex] = value;
            }

            return bits;
        }

        public static bool[] Convert(this int bits)
        {
            int numberOfBits = 32;
            bool[] bools = new bool[numberOfBits];

            for (int bitIndex = 0; bitIndex < numberOfBits; bitIndex++)
            {
                int bitMask = (1 << bitIndex);
                bool value = (bitMask == (bitMask & bits));
                bools[bitIndex] = value;
            }

            return bools;
        }

        #region GetMinValue()

        public static Nullable<T> GetMinValue<T>(this IEnumerable<T> collection)
            where T : struct, IComparable<T>
        {
            Nullable<T> minValue = null;
            foreach (T value in collection)
            {
                if (minValue.HasValue)
                {
                    if (value.CompareTo(minValue.Value) == -1)
                    { minValue = value; }
                }
                else
                { minValue = value; }
            }
            return minValue;
        }

        public static Nullable<T> GetMinValue<T>(this IEnumerable<T> collection, IComparer<T> comparer)
            where T : struct
        {
            Nullable<T> minValue = null;
            foreach (T value in collection)
            {
                if (minValue.HasValue)
                {
                    if (comparer.Compare(value, minValue.Value) == -1)
                    { minValue = value; }
                }
                else
                { minValue = value; }
            }
            return minValue;
        }

        public static Nullable<T_OUT> GetMinValue<T_OUT, T_IN>(this IEnumerable<T_IN> collection, Expression<Func<T_IN, T_OUT>> expression)
            where T_OUT : struct, IComparable<T_OUT>
        {
            string propertyName = ClassReflector.GetPropertyName<T_IN, T_OUT>(expression);
            return GetMinValue<T_OUT, T_IN>(collection, propertyName);
        }

        public static Nullable<T_OUT> GetMinValue<T_OUT, T_IN>(this IEnumerable<T_IN> collection, string propertyName)
            where T_OUT : struct, IComparable<T_OUT>
        {
            Type typeIn = typeof(T_IN);
            Type typeOut = typeof(T_OUT);
            PropertyInfo propertyInfo = typeIn.GetProperty(propertyName);

            if (propertyInfo == null)
            { throw new ApplicationException(); }
            if (!propertyInfo.PropertyType.Equals(typeOut))
            { throw new ApplicationException(); }

            List<T_OUT> propertyValues = new List<T_OUT>();
            foreach (T_IN input in collection)
            {
                propertyValues.Add((T_OUT)propertyInfo.GetValue(input, null));
            }

            return GetMinValue<T_OUT>(propertyValues);
        }

        #endregion

        #region GetMaxValue()

        public static Nullable<T> GetMaxValue<T>(this IEnumerable<T> collection)
            where T : struct, IComparable<T>
        {
            Nullable<T> maxValue = null;
            foreach (T value in collection)
            {
                if (maxValue.HasValue)
                {
                    if (value.CompareTo(maxValue.Value) == 1)
                    { maxValue = value; }
                }
                else
                { maxValue = value; }
            }
            return maxValue;
        }

        public static Nullable<T> GetMaxValue<T>(this IEnumerable<T> collection, IComparer<T> comparer)
            where T : struct
        {
            Nullable<T> maxValue = null;
            foreach (T value in collection)
            {
                if (maxValue.HasValue)
                {
                    if (comparer.Compare(value, maxValue.Value) == 1)
                    { maxValue = value; }
                }
                else
                { maxValue = value; }

            }
            return maxValue;
        }

        public static Nullable<T_OUT> GetMaxValue<T_OUT, T_IN>(this IEnumerable<T_IN> collection, Expression<Func<T_IN, T_OUT>> expression)
            where T_OUT : struct, IComparable<T_OUT>
        {
            string propertyName = ClassReflector.GetPropertyName<T_IN, T_OUT>(expression);
            return GetMaxValue<T_OUT, T_IN>(collection, propertyName);
        }

        public static Nullable<T_OUT> GetMaxValue<T_OUT, T_IN>(this IEnumerable<T_IN> collection, string propertyName)
            where T_OUT : struct, IComparable<T_OUT>
        {
            Type typeIn = typeof(T_IN);
            Type typeOut = typeof(T_OUT);
            PropertyInfo propertyInfo = typeIn.GetProperty(propertyName);

            if (propertyInfo == null)
            { throw new ApplicationException(); }
            if (!propertyInfo.PropertyType.Equals(typeOut))
            { throw new ApplicationException(); }

            List<T_OUT> propertyValues = new List<T_OUT>();
            foreach (T_IN input in collection)
            {
                propertyValues.Add((T_OUT)propertyInfo.GetValue(input, null));
            }

            return GetMaxValue<T_OUT>(propertyValues);
        }

        #endregion
    }
}